package org.ucas.kgraph.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.ucas.kgraph.dao.DocumentDao;
import org.ucas.kgraph.dao.RelationDao;
import org.ucas.kgraph.dao.TermDao;
import org.ucas.kgraph.dto.GraphDto;
import org.ucas.kgraph.dto.ListDto;
import org.ucas.kgraph.dto.TermGraphDto;
import org.ucas.kgraph.entity.File;
import org.ucas.kgraph.entity.Relation;
import org.ucas.kgraph.entity.RelationType;
import org.ucas.kgraph.entity.Term;
import org.ucas.kgraph.entity.TermGraph;
import org.ucas.kgraph.entity.DocFile;
import org.ucas.kgraph.entity.Document;
import org.ucas.kgraph.utils.exception.BadRequestException;
import org.ucas.kgraph.utils.exception.ConflictException;
import org.ucas.kgraph.utils.exception.ForbiddenException;
import org.ucas.kgraph.utils.exception.NotFoundException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import javax.management.relation.RelationTypeNotFoundException;


@Transactional
@Service
public class TermService {

  @Autowired
  private TermDao termDao;

  @Autowired
  private RelationDao relationDao;

  @Autowired
  private RelationService relationService;

  @Autowired
  private DocumentDao documentDao;
  
  public Term save(Term t) {
    t.sync();
    return termDao.save(t);
  }

  public Term safeSave(Term t){
    if(!exists(t.getId())) throw NotFoundException.Term_NotExists;
    ensure(t);
    t.sync();
    return termDao.save(t);
  }
  
  /**
   * 在保存之前先检查Term的合法性
   * @param t
   */
  public void ensure(Term t){

    if(t.getDefinition()==null) throw BadRequestException.Definition_Defect;


    if(t.getRootId()==null) throw ForbiddenException.INVALID_ROOTID;
    if(t.getRootId()<0) {
      //创建顶级节点
      t.setRootId(Long.valueOf(-1));
      t.setLevel(0);
      return;
    }
    Term root = load(t.getRootId());
    if(root == null || !root.getRootId().equals(Long.valueOf(-1))) throw ForbiddenException.INVALID_ROOTID;

  }

  public Term create(Term t){
    ensure(t);
    Term tmp = findByDefinition(t.getDefinition(),t.getRootId());
    if(tmp!=null) throw ConflictException.Term_Conflict;
    return save(t);
  }

  public Term createWithParent(Term t, Long parentId){
	 // System.out.println("------------------------------------1");
    Term term = create(t);

	 // System.out.println("------------------------------------2");
    if(parentId==null) return term;

	  //System.out.println("------------------------------------3");
    relationService.create(new Relation(term.getId(),parentId, RelationType.Parent));

	 // System.out.println("------------------------------------4");
    return term;
  }

  public Term load(Long id) {
    return termDao.findOne(id);
  }

  public boolean exists(Long id) {
    return termDao.exists(id);
  }

  public void delete(Long id) {
    relationDao.deleteByTerm(id);
    termDao.delete(id);
  }

  public void delete(Term term) {
    delete(term.getId());
  }



  public Term findByDefinition(String definition,Long rootId) {
    return termDao.findByDefinition(definition,rootId);
  }
  
  public Term rename(Long id, String name){
    Term term = load(id);
    if(term == null) throw NotFoundException.Term_NotExists;
    Term tmp = findByDefinition(name,term.getRootId());
    if(tmp!=null) throw ConflictException.Term_Conflict;
    term.setDefinition(name);
    return termDao.save(term);
  }
  
  
  public Term setDocument(Long id,Long documentId){
	    Term term = load(id);
	    if(term == null) throw NotFoundException.Term_NotExists;
	   
	    term.setDocumentId(documentId);
	    return termDao.save(term);
  }
  
  public void updateTags(Long id , List<String> tagList){
    Term term = load(id);
    if(term == null) throw NotFoundException.Term_NotExists;
    term.setTagList(tagList);

  }

  /**
   * 重新设定词条的等级
   * @param id
   */
  public void resetLevel(Long id){
    Term term = load(id);
    if(term==null) throw NotFoundException.Term_NotExists;
    Integer level = listParents(id,null).stream().map(t -> t.getLevel()).min(new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1,o2);
      }
    }).orElse(term.getLevel());
    term.setLevel(level);
    termDao.save(term);
  }


  /**
   * 批量加载terms
   * @param ids
   * @return
   */
  public List<Term> loadBatch(List<Long> ids){
    if(ids.size()==0) return new ArrayList<>();
    return termDao.loadBatch(ids);
  }

  /**
   * @param parentId
   * @param rootId
   * @return 返回在特定顶级主题下给定节点的所有直接儿子节点
   */
  public List<Term> listChildren(Long parentId, Long rootId){
    return filterByRoot(loadBatch(relationDao.childIds(parentId)),rootId);
  }

  public List<Term> listUnderRoot(Long rootId,Long file_id){
    if(rootId==null) rootId = Long.valueOf(-1);
    return termDao.listUnderRoot(rootId,file_id);
  }

  public List<Term> listRoots(Long file_id){return listUnderRoot(Long.valueOf(-1),file_id);}

  /**
   * @param id
   * @param rootId
   * @return 返回给定词条所有在特定顶级主题下关联的词条
   */
  public List<Term> listLinked(Long id, Long rootId){
    return filterByRoot(loadBatch(relationDao.linkedIds(id)),rootId);
  }

  /**
   * @param terms
   * @param rootId
   * @return terms中属于特定顶级主题的词条
   */
  public List<Term> filterByRoot(List<Term> terms, Long rootId){
    if(terms==null) return new ArrayList<Term>();
    if(rootId == null) return terms;
    return terms.stream()
            .filter(node -> {
              Long root = node.getRootId();
              if(rootId<0) return node.getRootId()<0; // 取所有顶级主题
              return root<0?node.getId().equals(rootId):root.equals(rootId);
            })
            .collect(Collectors.toList());
  }



  /**
   * 过滤Relation,它的两头必须均在#{terms}中
   * @param relations
   * @param termIds
   * @return
   */
  private List<Relation> filterByEndPoint(List<Relation> relations,List<Long> termIds){
    if(termIds==null) return relations;

    return relations.stream()
            .filter(r -> termIds.contains(r.getLeftEnd()) && termIds.contains(r.getRightEnd()))
            .collect(Collectors.toList());
  }

  /**
   * 返回以#{id}为根,depth为深度的图结构
   * @param id
   * @param depth
   * @return
   */
  public TermGraph descendant(Long id, Integer depth){
    Term center = load(id);
    Long rootId = center.getRootId()<0?center.getId():center.getRootId();
    depth = depth==null?Integer.MAX_VALUE:depth;
    if(center == null) throw NotFoundException.Term_NotExists;
    List<Long> termIds = new ArrayList<Long>(){
      {
        add(id);
      }
    };
    List<Long> childIds = new ArrayList<Long>(){
      {
        add(id);
      }
    };
    Set<Relation> relations = new HashSet<>();
    for (int i=1; i<depth; i++){
      List<Relation> tmp = relationDao.childsInBatch(childIds);
      relations.addAll(tmp);
      childIds = tmp.stream()
              .map(r->r.getLeftEnd())
              .filter(t -> !termIds.contains(t))  // 防止环的出现，迭代无法结束
              .collect(Collectors.toList());
      if(childIds.size()==0 || childIds==null) break;
      termIds.addAll(childIds);
    }
    List<Term> terms = termDao.loadBatch(termIds);

      /**暂时不检查子节点是否和父节点属于同一顶级主题，默认成立
      terms = filterByRoot(terms,root.getRootId());
      termIds = terms.stream().map(term -> term.getId()).collect(Collectors.toList());
       **/

    Integer minLevel = center.getLevel();
    Integer maxLevel = depth==Integer.MAX_VALUE?Integer.MAX_VALUE:minLevel+depth;

    /**
    List<Relation> links = relationDao.linksInBatchBetweenLevel(termIds,minLevel,maxLevel);
    links = filterByEndPoint(links,termIds);
     **/

    List<Relation> links =relationDao.strictLinksInBatch(termIds);
    relations.addAll(links);

    return new TermGraph(id,terms,relations);
  }
  
  public List<Term> finallLinked(Long id){
	  List<Long> termIds = new ArrayList<Long>(){
	      {
	        add(id);
	      }
	    };
	  List<Relation> linkRelations = relationDao.findAllLinks(id,RelationType.Link);
	  List<Term> terms = new LinkedList<Term>();
	  Term newTerm = new Term();
	  for (Relation relation : linkRelations) {
		Relation tRelation = new Relation();
		tRelation.setCategory(RelationType.Parent);
		tRelation.setRightEnd(id);
		if(relation.getLeftEnd().equals(id)){
			termIds.add(relation.getRightEnd());
			newTerm=termDao.getOne(relation.getRightEnd());
		}else{
			termIds.add(relation.getLeftEnd());
			newTerm=termDao.getOne(relation.getLeftEnd());
		}
		Term addTerm = new Term();
		addTerm.setCost(newTerm.getCost());
		String linkName = relation.getName().split(";")[0];
		addTerm.setDefinition("LK+"+linkName+":"+newTerm.getDefinition());
		addTerm.setDescription(newTerm.getDescription());
		addTerm.setDirection("right");
		addTerm.setDocumentId(newTerm.getDocumentId());
		addTerm.setFile_id(newTerm.getFile_id());
		addTerm.setId(newTerm.getId());
		addTerm.setLevel(newTerm.getLevel());
		addTerm.setRootId(newTerm.getRootId());
		addTerm.setTags(newTerm.getTags());
		addTerm.setTagList(newTerm.getTagList());
		addTerm.setTheme(newTerm.getTheme());
		terms.add(addTerm);
	  }
	  return terms;
  }
  
  public TermGraph descendantRelated(Long id,Integer depth){
	  Term center = load(id);
	  
	  List<Long> termIds = new ArrayList<Long>();
	  
	  List<Term> centers = termDao.searchCurrByDefinition(center.getDefinition());
	  
	  List<String> tagsList = new ArrayList<String>();
	  
	  boolean hasDocument=false;
	  
	  HashMap<Long,DocFile> docsMap = new HashMap<Long,DocFile>();
	  
	  List<DocFile> docsList = new ArrayList<DocFile>();
	  
	  Document documentNew = new Document();
	  
	  List<TermGraph> termGraphs = new LinkedList<TermGraph>();
	  List<Term> terms = new ArrayList<Term>();
	  Set<Relation> relations = new HashSet<>();
	  for (Term centerTerm : centers) {
		
		TermGraph centerIGraph = descendant(centerTerm.getId(), depth);
		for (Term term : centerIGraph.terms) {
			Term termTmp = new Term();
			termTmp.setCost(term.getCost());
			termTmp.setDefinition(term.getDefinition());
			termTmp.setDescription(term.getDescription());
			if(term.getDocumentId()!=null){
				Document document = new Document();
				document = documentDao.findOne(term.getDocumentId());
				Document newDocument = new Document();
				newDocument.setContent(document.getContent());
				List<DocFile> docs = new ArrayList<>();
				for(DocFile df:document.getDocList()){
					docs.add(df);
				}
				newDocument.setDocList(docs);
				documentDao.save(newDocument);
				termTmp.setDocumentId(newDocument.getId());
			}
			termTmp.setId(term.getId());
			termTmp.setLevel(term.getLevel());
			termTmp.setRootId(term.getRootId());
			termTmp.setTagList(term.getTagList());
			termTmp.setTags(term.getTags());
			termTmp.setTheme(term.getTheme());
			
			if(termTmp.getId().equals(centerIGraph.rootId)){
				
			}else{
				termTmp.setDirection("right");
				termIds.add(term.getId());
				terms.add(termTmp);
			}
		}
		for (Relation relation : centerIGraph.relations) {
			Relation relationTmp = new Relation();
			relationTmp.setCategory(relation.getCategory());
			relationTmp.setId(relation.getId());
			relationTmp.setLeftEnd(relation.getLeftEnd());
			relationTmp.setLevel(relation.getLevel());
			relationTmp.setName(relation.getName());
			relationTmp.setRightEnd(relation.getRightEnd());
			if(relationTmp.getRightEnd().equals(centerIGraph.rootId)){
				relationTmp.setRightEnd(id);
			}
			relations.add(relationTmp);
		}
	  }
	  documentNew.setContent("");
	  for (Term centerTerm : centers) {
		  for (String tag : centerTerm.getTagList()) {
			  
			  
			  if(tag.length()>0&&!tagsList.contains(tag)){
				  tagsList.add(tag);
			  }
		  }
		  if(centerTerm.getDocumentId()!=null){
			  Document document = new Document();
			  document = documentDao.findOne(centerTerm.getDocumentId());
			  if(document!=null&&document.getContent().length()>0){
				  documentNew.setContent(documentNew.getContent()+document.getContent());
			  }
		      for(DocFile df:document.getDocList()){
		    	  if(docsMap.containsKey(df.getId())){
		    		  
		    	  }else{
		    		  docsMap.put(df.getId(),df);
		    	  }
		      }

			  
		      hasDocument=true;
		  }
	  }
	  if(tagsList.size()>0){
		  for (String tag : tagsList) {
			  List<Term> tagCenters = termDao.searchByTag(tag);
			  for (Term tagCenterTerm : tagCenters) {
				  if(termIds.contains(tagCenterTerm.getId())||tagCenterTerm.getDefinition().equals(center.getDefinition())){
					  continue;
				  }
				  termIds.add(tagCenterTerm.getId());
				  Term termTmp = new Term();
				  termTmp.setCost(tagCenterTerm.getCost());
				  
				  termTmp.setDefinition(tagCenterTerm.getDefinition());
				  termTmp.setDescription(tagCenterTerm.getDescription());
				  if(tagCenterTerm.getDocumentId()!=null){
					  Document document = new Document();
					  document = documentDao.findOne(tagCenterTerm.getDocumentId());
					  Document newDocument = new Document();
				      newDocument.setContent(document.getContent());
				      List<DocFile> docs = new ArrayList<>();
						for(DocFile df:document.getDocList()){
							docs.add(df);
						}
						newDocument.setDocList(docs);
					  documentDao.save(newDocument);
					  termTmp.setDocumentId(newDocument.getId());
				  }
				  termTmp.setId(tagCenterTerm.getId());
				  termTmp.setLevel(tagCenterTerm.getLevel());
				  termTmp.setRootId(tagCenterTerm.getRootId());
				  termTmp.setTagList(tagCenterTerm.getTagList());
			      termTmp.setTags(tagCenterTerm.getTags());
				  termTmp.setTheme(tagCenterTerm.getTheme());
				  termTmp.setDirection("left");
				  terms.add(termTmp);
				  Relation relationTmp = new Relation();
				  relationTmp.setCategory(RelationType.Parent);
				  relationTmp.setLeftEnd(termTmp.getId());
				  relationTmp.setRightEnd(center.getId());
			      relations.add(relationTmp);
			  }
		  }
	  }

	  for (Term centerTerm : centers) {
		  List<Term> linkedTerms = finallLinked(centerTerm.getId());
		  for (Term linkedTerm : linkedTerms) {
			  if(termIds.contains(linkedTerm.getId())||linkedTerm.getDefinition().equals(center.getDefinition())){
				  continue;
			  }
			  termIds.add(linkedTerm.getId());
			  Term termTmp = new Term();
			  termTmp.setCost(linkedTerm.getCost());
			  termTmp.setDefinition(linkedTerm.getDefinition());
			  termTmp.setDescription(linkedTerm.getDescription());
			  if(linkedTerm.getDocumentId()!=null){
				  Document document = new Document();
				  document = documentDao.findOne(linkedTerm.getDocumentId());
				  Document newDocument = new Document();
			      newDocument.setContent(document.getContent());
				  documentDao.save(newDocument);
				  termTmp.setDocumentId(newDocument.getId());
			  }
			  termTmp.setId(linkedTerm.getId());
			  termTmp.setLevel(linkedTerm.getLevel());
			  termTmp.setRootId(linkedTerm.getRootId());
			  termTmp.setTagList(linkedTerm.getTagList());
		      termTmp.setTags(linkedTerm.getTags());
			  termTmp.setTheme(linkedTerm.getTheme());
			  termTmp.setDirection("right");
			  terms.add(termTmp);
			  Relation relationTmp = new Relation();
			  relationTmp.setCategory(RelationType.Parent);
			  relationTmp.setLeftEnd(termTmp.getId());
			  relationTmp.setRightEnd(center.getId());
		      relations.add(relationTmp);
		  }
	  }
	  Term newCenter = new Term();
	  //newCenter.setCost(center.getCost());
	  newCenter.setDefinition(center.getDefinition());
	  newCenter.setDescription(center.getDescription());
	  if(hasDocument){
		  for(DocFile df:docsMap.values()){
			  docsList.add(df);
		  }
		  documentNew.setDocList(docsList);
		  documentDao.save(documentNew);
		  newCenter.setDocumentId(documentNew.getId());
	  }
	  
	  newCenter.setId(id);
	  newCenter.setLevel(center.getLevel());
	  newCenter.setRootId(center.getRootId());
	  newCenter.setTagList(tagsList);
	  String newtags="";
	  if(tagsList!=null&&tagsList.size()!=0){
		  for (String tag : tagsList) {
			  if(newtags.length()==0){
				  newtags+=tag;
			  }else{
				  newtags=newtags+";"+tag;
			  }
			  
		  }
	  }
	  newCenter.setTags(newtags);
	  terms.add(newCenter);
	  return new TermGraph(id,terms,relations);
  }
  /**
   * @param id
   * @return 给定顶级主题下的所有父亲节点词条
   */
  public List<Term> listParents(Long id, Long rootId){
    return filterByRoot(loadBatch(relationDao.parentIds(id)),rootId);
  }

  /**
   * 当前情况下一个顶级主题只有一个父亲节点，所以稍微对上个方法做一点改动
   * @param id
   * @param rootId
   * @return
   */
  public Term parent(Long id, Long rootId){
    List<Term> parents = listParents(id,rootId);
    return parents.size()>0?parents.get(0):null;
  }
  /**
   * 根据definition搜索
   * @param rootId
   * @return
   */
  public List<Term> searchByDefinition(String definition){
	  return termDao.searchByDefinition(definition);
  }
  //{"meta":{"name":"demo","author":"zhangyipin","version":"0.2"},"format":"node_array","data":[{"id":"0","topic":"322 ","expanded":true,"isroot":true,"root":true}]}
  public void saveGraph(String content, Long file_id) throws JsonParseException, JsonMappingException, IOException {
	ObjectMapper mapper = new ObjectMapper();  
	mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);  
	GraphDto graph = mapper.readValue(content, GraphDto.class); 
	for(int i=0;i<graph.getData().size();i++){
		 if(graph.getData().get(i).getParentid()==null){
			 HashMap<String, Long> hashMap = new HashMap<String, Long>(); 
			 this.saveNode(hashMap,graph,i,null,Long.valueOf(-1),file_id);
			 this.saveLink(hashMap,graph);
		 }
	}
  }
  public void deleteAll(Long parentId,Long rootId){
	  List<Term> terms = this.listChildren(parentId, rootId);
	  
	  for(int i=0;i<terms.size();i++){
		  
		 // System.out.println("==================================="+terms.get(i).getId());
		  deleteAll(terms.get(i).getId(),rootId);
	  }
	  this.delete(parentId);
  }
  
  public void cutThis(Long parentId,Long rootId){
	  Term terms = this.parent(parentId, rootId);

  }
  public void saveLink(HashMap<String, Long> hashMap,GraphDto graph){
	  for(int k=0;k<graph.getData().size();k++){
		  if(graph.getData().get(k).getLink()!=null){
			  for(int i=0;i<graph.getData().get(k).getLink().size();i++){
				  Relation relation = new Relation();
				  relation=graph.getData().get(k).getLink().get(i).writeto(hashMap.get(graph.getData().get(k).getId()),hashMap.get(graph.getData().get(k).getLink().get(i).getTo()) ,relation);
				  relationDao.save(relation);
			  }
		  }
	  }
  }
  public void saveNode(HashMap<String, Long> hashMap,GraphDto graph,int k,Long parentid,Long rootId,Long file_id){
	  Term newterm=new Term();
	  newterm=graph.getData().get(k).writeto(newterm);
	  newterm.setRootId(rootId);
	  newterm.setFile_id(file_id);
	  if(parentid==null){
		  this.create(newterm);
		  rootId=newterm.getId();
	  }else{
		  this.createWithParent(newterm,parentid);
	  }
	  
	  hashMap.put( graph.getData().get(k).getId(),newterm.getId());
	  
	  for(int i=0;i<graph.getData().size();i++){
		
		  if(graph.getData().get(i).getParentid()!=null){
			  if(graph.getData().get(i).getParentid().equals(graph.getData().get(k).getId())){
				  this.saveNode(hashMap,graph,i,newterm.getId(),rootId,file_id);
			  }
		  }
	  }
  } 
  public void refile(Long id, Long file_id) {
	   Term center = load(id);
	   Long rootId = center.getRootId()<0?center.getId():center.getRootId();
	  
	   if(center == null) throw NotFoundException.Term_NotExists;
	   List<Long> termIds = new ArrayList<Long>(){
	     {
	       add(id);
	     }
	   };
	   List<Long> childIds = new ArrayList<Long>(){
	     {
	       add(id);
	     }
	    };
	    Set<Relation> relations = new HashSet<>();
	    for (int i=1; true; i++){
	      List<Relation> tmp = relationDao.childsInBatch(childIds);
	      relations.addAll(tmp);
	      childIds = tmp.stream()
	              .map(r->r.getLeftEnd())
	              .filter(t -> !termIds.contains(t))  // 防止环的出现，迭代无法结束
	              .collect(Collectors.toList());
	      if(childIds.size()==0 || childIds==null) break;
	      termIds.addAll(childIds);
	    }
	    for(int i = 0;i<termIds.size();i++){
	    	Term newTerm = termDao.getOne(termIds.get(i));
	    	newTerm.setFile_id(file_id);
	    	termDao.save(newTerm);
	    }
  }
  public Term redirection(Long id, String direction) {
	// TODO Auto-generated method stub
	  Term term = load(id);
	  if(term == null) throw NotFoundException.Term_NotExists;
	    
	  term.setDirection(direction);
	  return termDao.save(term);
  }

  public Term recost(Long id, Float cost) {
	Term term = load(id);
	  if(term == null) throw NotFoundException.Term_NotExists;
	    
	  term.setCost(cost);
	  return termDao.save(term);
  }

  public Term retheme(Long id, String theme) {
	// TODO Auto-generated method stub
	Term term = load(id);
    term.setTheme(theme);
    return termDao.save(term);
  }
  public List<Term> searchRootByDefinition(String definition, Long fileId){
	  
	  return termDao.searchRootByDefinition(definition,fileId,Long.valueOf(-1));
  }

  
}
